/*
 *    Copyright 2009-2023 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       https://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.ryan.builder;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.jdbc.AbstractSQL;
import org.apache.ibatis.jdbc.SQL;
import org.apache.ibatis.ryan.Tools;
import org.apache.ibatis.ryan.utils.Assert;
import org.apache.ibatis.ryan.utils.PageHoder;

public class QueryBuilder<E> implements Builder<E> {

  public static String param3 = "param3";

  private SQL sql = new SQL();
  private Class<?> resultType;
  private Map<String, Object> params = new HashMap<>();
  private Class<?> join;
  private String joinTable;
  private boolean left;
  private int groupId = -1;

  public String getSql() {
    return sql.toString();
  }

  private QueryBuilder(Class<E> resultType, int groupId) {
    this.resultType = resultType;
    this.groupId = groupId;
  }

  public static <T> QueryBuilder<T> build(Class<T> resultType) {
    return build(resultType, null, -1);
  }

  public static <T> QueryBuilder<T> build(Class<T> resultType, String table) {
    return build(resultType, table, -1);
  }

  public static <T> QueryBuilder<T> build(Class<T> resultType, int groupId) {
    return build(resultType, null, groupId);
  }

  public static <Bean> QueryBuilder<Bean> build(Class<Bean> resultType, String table, int hitGroupId) {
    QueryBuilder<Bean> criteriaQuery = new QueryBuilder<>(resultType, hitGroupId);
    criteriaQuery.initByTable(table);
    return criteriaQuery;
  }

  private void initByTable(String table) {
    if (StringUtils.isNotBlank(table)) {
      table = Tools.toUnderLine(table);
    } else if (resultType != null) {
      table = Tools.tableName(resultType);
    }
    if (groupId > 0) {
      String[] cols = Tools.fieldToCols(resultType, table, groupId);
      if (cols.length == 0) {
        throw new IllegalArgumentException("字段返回列表为空！请使用 Column注解");
      }
      sql.SELECT(cols);
    } else {
      sql.SELECT(String.format("%s.*", new Object[] { table }));
    }
    sql.FROM(table);
  }

  public Class getResultType() {
    return resultType;
  }

  public Map<String, Object> getParams() {
    return params;
  }

  public QueryBuilder<E> or() {
    sql.OR();
    return this;
  }

  public QueryBuilder<E> ore() {
    sql.ORE();
    return this;
  }

  public QueryBuilder<E> whereIsNull(String propName) {
    String colName = Tools.toUnderLine(propName);
    sql.WHERE(String.format(" %s is null", new Object[] { colName }));
    return this;
  }

  public QueryBuilder<E> whereNotNull(String propName) {
    String colName = Tools.toUnderLine(propName);
    sql.WHERE(String.format(" %s is not null", new Object[] { colName }));
    return this;
  }

  public QueryBuilder<E> whereEq(String propName, Object value) {
    return where(Tools.eq, propName, value);
  }

  public QueryBuilder<E> whereNotEq(String propName, Object value) {
    return where(Tools.notEq, propName, value);
  }

  private QueryBuilder<E> where(String op, String propName, Object value) {
    if (value == null || StringUtils.isBlank(value.toString())) {
      return this;
    }
    String varName = Tools.buildVar(propName, params, null, value);
    String colName = Tools.toUnderLine(propName);
    sql.WHERE(String.format(op, new Object[] { colName, param3, varName }));
    return this;
  }

  public QueryBuilder<E> whereLike(String propName, String value) {
    return where(Tools.like, propName, value);
  }

  public QueryBuilder<E> whereLow(String propName, Object value) {
    return where(Tools.low, propName, value);
  }

  public QueryBuilder<E> whereLowEq(String propName, Object value) {
    return where(Tools.lowEq, propName, value);
  }

  public QueryBuilder<E> whereIn(String propName, List value) {
    if (value.isEmpty()) {
      return this;
    }
    Tools.staticWhereIn(propName, param3, value, (AbstractSQL) sql, params);
    return this;
  }

  public QueryBuilder<E> whereNotIn(String propName, List<?> value) {
    if (value.isEmpty()) {
      return this;
    }
    Tools.staticWhereNotIn(propName, param3, value, (AbstractSQL) sql, params);
    return this;
  }

  public QueryBuilder<E> ande() {
    sql.ANDE();
    return this;
  }

  public QueryBuilder<E> startPt() {
    sql.START_PT();
    return this;
  }

  public QueryBuilder<E> endPt() {
    sql.END_PT();
    return this;
  }

  public QueryBuilder<E> whereBig(String propName, Object value) {
    return where(Tools.big, propName, value);
  }

  public QueryBuilder<E> whereBigEq(String propName, Object value) {
    return where(Tools.bigEq, propName, value);
  }

  public QueryBuilder<E> join(Class<?> tableClass) {
    join = tableClass;
    return this;
  }

  public QueryBuilder<E> join(Class<?> tableClass, int hitGroupId) {
    String[] cols = Tools.fieldToCols(tableClass, Tools.tableName(tableClass), hitGroupId);
    sql.SELECT(cols);
    join = tableClass;
    return this;
  }

  public QueryBuilder<E> join(Class<?> resultType, String table, int hitGroupId) {
    String[] cols = Tools.fieldToCols(resultType, table, hitGroupId);
    sql.SELECT(cols);
    joinTable = table;
    return this;
  }

  public QueryBuilder<E> leftJoin(Class<?> resultType) {
    join = resultType;
    left = true;
    return this;
  }

  public QueryBuilder<E> leftJoin(Class<?> resultType, int hitGroupId) {
    String[] cols = Tools.fieldToCols(resultType, Tools.tableName(resultType), hitGroupId);
    sql.SELECT(cols);
    join = resultType;
    left = true;
    return this;
  }

  public QueryBuilder<E> leftJoin(Class<?> resultType, String table, int hitGroupId) {
    String[] cols = Tools.fieldToCols(resultType, table, hitGroupId);
    sql.SELECT(cols);
    joinTable = table;
    left = true;
    return this;
  }

  public QueryBuilder<E> on(String tableOn1, String tableOn2) {
    Assert.check((join != null || joinTable != null),
        "\n先调用org.lufei.ibatis.builder.QueryBuilder.join(CriteriaQuery.java)设定join目标表");
    String format = null;

    if (StringUtils.isNotBlank(joinTable)) {
      format = String.format(" %s on %s = %s ",
          new Object[] { Tools.toUnderLine(joinTable), Tools.toUnderLine(tableOn1), Tools.toUnderLine(tableOn2) });
    } else {

      format = String.format(" %s on %s = %s ", new Object[] { Tools.toUnderLine(Tools.tableName(join)),
          Tools.toUnderLine(tableOn1), Tools.toUnderLine(tableOn2) });
    }

    if (left) {
      sql.LEFT_OUTER_JOIN(format);
    } else {
      sql.INNER_JOIN(format);
    }
    joinTable = null;
    join = null;
    left = false;
    return this;
  }

  public QueryBuilder<E> orderByDesc(String propName) {
    if (StringUtils.isBlank(propName)) {
      return this;
    }
    sql.ORDER_BY(String.format("%s DESC", new Object[] { Tools.toUnderLine(propName) }));
    return this;
  }

  public QueryBuilder<E> orderByAsc(String propName) {
    if (StringUtils.isBlank(propName)) {
      return this;
    }
    sql.ORDER_BY(String.format("%s ASC", new Object[] { Tools.toUnderLine(propName) }));
    return this;
  }

  public QueryBuilder<E> orderBy(String propNameAndOrder) {
    if (StringUtils.isBlank(propNameAndOrder)) {
      return this;
    }
    propNameAndOrder = propNameAndOrder.replaceAll("order\\s+by", "");
    sql.ORDER_BY(propNameAndOrder);
    return this;
  }

  public QueryBuilder<E> setPage(int pageNo, int pageSize) {
    PageHoder.setPageNo(Integer.valueOf(pageNo));
    PageHoder.setPageSize(Integer.valueOf(pageSize));
    return this;
  }

  public <W> QueryBuilder<E> whereBean(W whereBean) {
    return whereBean(whereBean, null);
  }

  public <W> QueryBuilder<E> whereBean(W whereBean, String table) {
    List<Field> fields = Tools.getInstanceFields(whereBean.getClass());
    if (fields.isEmpty()) {
      return this;
    }

    fields.forEach(field -> {
      Object fieldValue = Tools.getFieldValue(whereBean, field);
      String pre = (table != null) ? table : Tools.tableName(whereBean.getClass());
      whereEq(pre + "." + field.getName(), fieldValue);
    });
    return this;
  }

  public QueryBuilder<E> appendWhere(String sqlString) {
    sql.WHERE(sqlString);
    return this;
  }

  public <B> QueryBuilder<E> toCount(Class<B> resultType, String sqlFunc) {
    this.resultType = resultType;
    sql.CLEAR_SELECT();
    sql.SELECT(sqlFunc);
    return this;
  }
}
